#ifndef TIEC_TREE_COPIER_H
#define TIEC_TREE_COPIER_H

#include "macro.h"
#include "tree.h"

namespace NS_TIEC {
  class TreeCopier : public TreeScanner<PTR<Tree>, NullValue&> {
  public:
    TreeCopier() = default;

    PTR<Tree> copy(const PTR<Tree>& tree) {
      return scan(tree, kGlobalNullValue);
    }

    PTR<Tree> visitIdentifierTree(const PTR<IdentifierTree>& tree, NullValue& param) override {
      auto new_tree = PTR<IdentifierTree>();
      new_tree->origin = tree->origin;
      new_tree->name = tree->name;
      return new_tree;
    }

    PTR<Tree> visitAnnotationTree(const PTR<AnnotationTree>& tree, NullValue& param) override {
      auto new_tree = PTR<AnnotationTree>();
      new_tree->origin = tree->origin;
      new_tree->name = std::static_pointer_cast<IdentifierTree>(scan(tree->name, param));
      new_tree->arg = std::static_pointer_cast<ExpressionTree>(scan(tree->arg, param));
      return new_tree;
    }

    PTR<Tree> visitBlockTree(const PTR<BlockTree>& tree, NullValue& param) override {
      auto new_tree = PTR<BlockTree>();
      new_tree->origin = tree->origin;
      new_tree->statements = copyList<StatementTree>(tree->statements, param);
      return new_tree;
    }

    PTR<Tree> visitVariableTree(const PTR<VariableTree>& tree, NullValue& param) override {
      auto new_tree = PTR<VariableTree>();
      new_tree->origin = tree->origin;
      new_tree->modifiers = tree->modifiers;
      new_tree->ident = std::static_pointer_cast<IdentifierTree>(scan(tree->ident, param));
      new_tree->annotations = copyList<AnnotationTree>(tree->annotations, param);
      new_tree->comment = tree->comment;
      new_tree->type = std::static_pointer_cast<ExpressionTree>(scan(tree->type, param));
      new_tree->initializer = scan(tree->initializer, param);
      new_tree->is_parameter = tree->is_parameter;
      return new_tree;
    }

    PTR<Tree> visitMethodTree(const PTR<MethodTree>& tree, NullValue& param) override {
      auto new_tree = PTR<MethodTree>();
      new_tree->origin = tree->origin;
      new_tree->modifiers = tree->modifiers;
      new_tree->ident = std::static_pointer_cast<IdentifierTree>(scan(tree->ident, param));
      new_tree->annotations = copyList<AnnotationTree>(tree->annotations, param);
      new_tree->comment = tree->comment;
      new_tree->method_kind = tree->method_kind;
      new_tree->parameters = copyList<VariableTree>(tree->parameters, param);
      new_tree->return_type = std::static_pointer_cast<ExpressionTree>(scan(tree->return_type, param));
      new_tree->body = std::static_pointer_cast<BlockTree>(scan(tree->body, param));
      return new_tree;
    }

    PTR<Tree> visitEventTree(const PTR<EventTree>& tree, NullValue& param) override {
      auto new_tree = PTR<EventTree>();
      new_tree->origin = tree->origin;
      new_tree->modifiers = tree->modifiers;
      new_tree->ident = std::static_pointer_cast<IdentifierTree>(scan(tree->ident, param));
      new_tree->annotations = copyList<AnnotationTree>(tree->annotations, param);
      new_tree->comment = tree->comment;
      new_tree->method_kind = tree->method_kind;
      new_tree->parameters = copyList<VariableTree>(tree->parameters, param);
      new_tree->return_type = std::static_pointer_cast<ExpressionTree>(scan(tree->return_type, param));
      new_tree->body = std::static_pointer_cast<BlockTree>(scan(tree->body, param));
      new_tree->event_obj = std::static_pointer_cast<ExpressionTree>(scan(tree->event_obj, param));
      return new_tree;
    }

    PTR<Tree> visitCodeLineTree(const PTR<CodeLineTree>& tree, NullValue& param) override {
      auto new_tree = PTR<CodeLineTree>();
      new_tree->origin = tree->origin;
      new_tree->base_indent = tree->base_indent;
      new_tree->trees = copyList<Tree>(tree->trees, param);
      return new_tree;
    }

    PTR<Tree> visitCodeTree(const PTR<CodeTree>& tree, NullValue& param) override {
      auto new_tree = PTR<CodeTree>();
      new_tree->origin = tree->origin;
      new_tree->code_kind = tree->code_kind;
      new_tree->tag = std::static_pointer_cast<IdentifierTree>(scan(tree->tag, param));
      new_tree->lines = copyList<CodeLineTree>(tree->lines, param);
      return new_tree;
    }

    PTR<Tree> visitClassTree(const PTR<ClassTree>& tree, NullValue& param) override {
      auto new_tree = PTR<ClassTree>();
      new_tree->origin = tree->origin;
      new_tree->modifiers = tree->modifiers;
      new_tree->ident = std::static_pointer_cast<IdentifierTree>(scan(tree->ident, param));
      new_tree->annotations = copyList<AnnotationTree>(tree->annotations, param);
      new_tree->comment = tree->comment;
      new_tree->super = std::static_pointer_cast<ExpressionTree>(scan(tree->super, param));
      new_tree->templates = copyList<IdentifierTree>(tree->templates, param);
      new_tree->members = copyList<AnnotatedTree>(tree->members, param);
      new_tree->codes = copyList<CodeTree>(tree->codes, param);
      return new_tree;
    }

    PTR<Tree> visitCompilationUnitTree(const PTR<CompilationUnitTree>& tree, NullValue& param) override {
      auto new_tree = PTR<CompilationUnitTree>();
      new_tree->origin = tree->origin;
      new_tree->modifiers = tree->modifiers;
      new_tree->ident = std::static_pointer_cast<IdentifierTree>(scan(tree->ident, param));
      new_tree->annotations = copyList<AnnotationTree>(tree->annotations, param);
      new_tree->comment = tree->comment;
      new_tree->pkg = std::static_pointer_cast<ExpressionTree>(scan(tree->pkg, param));
      new_tree->defs = copyList<Tree>(tree->defs, param);
      return new_tree;
    }

    PTR<Tree> visitExpressionStatementTree(const PTR<ExpressionStatementTree>& tree, NullValue& param) override {
      auto new_tree = PTR<ExpressionStatementTree>();
      new_tree->origin = tree->origin;
      new_tree->expr = std::static_pointer_cast<ExpressionTree>(scan(tree->expr, param));
      return new_tree;
    }

    PTR<Tree> visitIfTree(const PTR<IfTree>& tree, NullValue& param) override {
      auto new_tree = PTR<IfTree>();
      new_tree->origin = tree->origin;
      new_tree->condition = std::static_pointer_cast<ExpressionTree>(scan(tree->condition, param));
      new_tree->then_part = std::static_pointer_cast<BlockTree>(scan(tree->then_part, param));
      new_tree->else_part = std::static_pointer_cast<StatementTree>(scan(tree->else_part, param));
      return new_tree;
    }

    PTR<Tree> visitCaseTree(const PTR<CaseTree>& tree, NullValue& param) override {
      auto new_tree = PTR<CaseTree>();
      new_tree->origin = tree->origin;
      new_tree->conditions = copyList<ExpressionTree>(tree->conditions, param);
      new_tree->body = std::static_pointer_cast<BlockTree>(scan(tree->body, param));
      return new_tree;
    }

    PTR<Tree> visitSwitchTree(const PTR<SwitchTree>& tree, NullValue& param) override {
      auto new_tree = PTR<SwitchTree>();
      new_tree->origin = tree->origin;
      new_tree->selector = std::static_pointer_cast<ExpressionTree>(scan(tree->selector, param));
      new_tree->cases = copyList<CaseTree>(tree->cases, param);
      return new_tree;
    }

    PTR<Tree> visitForLoopTree(const PTR<ForLoopTree>& tree, NullValue& param) override {
      auto new_tree = PTR<ForLoopTree>();
      new_tree->origin = tree->origin;
      new_tree->target = std::static_pointer_cast<ExpressionTree>(scan(tree->target, param));
      new_tree->start = std::static_pointer_cast<ExpressionTree>(scan(tree->start, param));
      new_tree->end = std::static_pointer_cast<ExpressionTree>(scan(tree->end, param));
      new_tree->step = std::static_pointer_cast<ExpressionTree>(scan(tree->step, param));
      new_tree->body = std::static_pointer_cast<BlockTree>(scan(tree->body, param));
      return new_tree;
    }

    PTR<Tree> visitForEachTree(const PTR<ForEachTree>& tree, NullValue& param) override {
      auto new_tree = PTR<ForEachTree>();
      new_tree->origin = tree->origin;
      new_tree->source = std::static_pointer_cast<ExpressionTree>(scan(tree->source, param));
      new_tree->var1 = std::static_pointer_cast<ExpressionTree>(scan(tree->var1, param));
      new_tree->var2 = std::static_pointer_cast<ExpressionTree>(scan(tree->var2, param));
      new_tree->body = std::static_pointer_cast<BlockTree>(scan(tree->body, param));
      return new_tree;
    }

    PTR<Tree> visitWhileLoopTree(const PTR<WhileLoopTree>& tree, NullValue& param) override {
      auto new_tree = PTR<WhileLoopTree>();
      new_tree->origin = tree->origin;
      new_tree->condition = std::static_pointer_cast<ExpressionTree>(scan(tree->condition, param));
      new_tree->body = std::static_pointer_cast<BlockTree>(scan(tree->body, param));
      return new_tree;
    }

    PTR<Tree> visitLiteralTree(const PTR<LiteralTree>& tree, NullValue& param) override {
      auto new_tree = PTR<LiteralTree>();
      new_tree->origin = tree->origin;
      new_tree->value = tree->value;
      return new_tree;
    }

    PTR<Tree> visitArrayTypeTree(const PTR<ArrayTypeTree>& tree, NullValue& param) override {
      auto new_tree = PTR<ArrayTypeTree>();
      new_tree->origin = tree->origin;
      new_tree->expr = std::static_pointer_cast<ExpressionTree>(scan(tree->expr, param));
      new_tree->size = std::static_pointer_cast<ExpressionTree>(scan(tree->size, param));
      return new_tree;
    }

    PTR<Tree> visitTemplateTypeTree(const PTR<TemplateTypeTree>& tree, NullValue& param) override {
      auto new_tree = PTR<TemplateTypeTree>();
      new_tree->origin = tree->origin;
      new_tree->expr = std::static_pointer_cast<ExpressionTree>(scan(tree->expr, param));
      new_tree->args = copyList<ExpressionTree>(tree->args, param);
      return new_tree;
    }

    PTR<Tree> visitFieldAccessTree(const PTR<FieldAccessTree>& tree, NullValue& param) override {
      auto new_tree = PTR<FieldAccessTree>();
      new_tree->origin = tree->origin;
      new_tree->prefix = std::static_pointer_cast<ExpressionTree>(scan(tree->prefix, param));
      new_tree->ident = std::static_pointer_cast<IdentifierTree>(scan(tree->ident, param));
      return new_tree;
    }

    PTR<Tree> visitMethodInvokeTree(const PTR<MethodInvokeTree>& tree, NullValue& param) override {
      auto new_tree = PTR<MethodInvokeTree>();
      new_tree->origin = tree->origin;
      new_tree->method = std::static_pointer_cast<ExpressionTree>(scan(tree->method, param));
      new_tree->args = copyList<ExpressionTree>(tree->args, param);
      return new_tree;
    }

    PTR<Tree> visitNewClassTree(const PTR<NewClassTree>& tree, NullValue& param) override {
      auto new_tree = PTR<NewClassTree>();
      new_tree->origin = tree->origin;
      new_tree->class_name = std::static_pointer_cast<ExpressionTree>(scan(tree->class_name, param));
      return new_tree;
    }

    PTR<Tree> visitNewArrayTree(const PTR<NewArrayTree>& tree, NullValue& param) override {
      auto new_tree = PTR<NewArrayTree>();
      new_tree->origin = tree->origin;
      new_tree->elements = copyList<ExpressionTree>(tree->elements, param);
      return new_tree;
    }

    PTR<Tree> visitArrayAccessTree(const PTR<ArrayAccessTree>& tree, NullValue& param) override {
      auto new_tree = PTR<ArrayAccessTree>();
      new_tree->origin = tree->origin;
      new_tree->selected = std::static_pointer_cast<ExpressionTree>(scan(tree->selected, param));
      new_tree->index = std::static_pointer_cast<ExpressionTree>(scan(tree->index, param));
      return new_tree;
    }

    PTR<Tree> visitBreakTree(const PTR<BreakTree>& tree, NullValue& param) override {
      auto new_tree = PTR<BreakTree>();
      new_tree->origin = tree->origin;
      return new_tree;
    }

    PTR<Tree> visitContinueTree(const PTR<ContinueTree>& tree, NullValue& param) override {
      auto new_tree = PTR<ContinueTree>();
      new_tree->origin = tree->origin;
      return new_tree;
    }

    PTR<Tree> visitReturnTree(const PTR<ReturnTree>& tree, NullValue& param) override {
      auto new_tree = PTR<ReturnTree>();
      new_tree->origin = tree->origin;
      new_tree->value = std::static_pointer_cast<ExpressionTree>(scan(tree->value, param));
      return new_tree;
    }

    PTR<Tree> visitSubscribeEventTree(const PTR<SubscribeEventTree>& tree, NullValue& param) override {
      auto new_tree = PTR<SubscribeEventTree>();
      new_tree->origin = tree->origin;
      new_tree->vars = copyList<IdentifierTree>(tree->vars, param);
      return new_tree;
    }

    PTR<Tree> visitTypeCastTree(const PTR<TypeCastTree>& tree, NullValue& param) override {
      auto new_tree = PTR<TypeCastTree>();
      new_tree->origin = tree->origin;
      new_tree->expr = std::static_pointer_cast<ExpressionTree>(scan(tree->expr, param));
      new_tree->type = std::static_pointer_cast<ExpressionTree>(scan(tree->type, param));
      return new_tree;
    }

    PTR<Tree> visitAssignTree(const PTR<AssignTree>& tree, NullValue& param) override {
      auto new_tree = PTR<AssignTree>();
      new_tree->origin = tree->origin;
      new_tree->var = std::static_pointer_cast<ExpressionTree>(scan(tree->var, param));
      new_tree->value = std::static_pointer_cast<ExpressionTree>(scan(tree->value, param));
      return new_tree;
    }

    PTR<Tree> visitExchangeTree(const PTR<ExchangeTree>& tree, NullValue& param) override {
      auto new_tree = PTR<ExchangeTree>();
      new_tree->origin = tree->origin;
      new_tree->left = std::static_pointer_cast<ExpressionTree>(scan(tree->left, param));
      new_tree->right = std::static_pointer_cast<ExpressionTree>(scan(tree->right, param));
      return new_tree;
    }

    PTR<Tree> visitInstanceOfTree(const PTR<InstanceOfTree>& tree, NullValue& param) override {
      auto new_tree = PTR<InstanceOfTree>();
      new_tree->origin = tree->origin;
      new_tree->expr = std::static_pointer_cast<ExpressionTree>(scan(tree->expr, param));
      new_tree->type = std::static_pointer_cast<ExpressionTree>(scan(tree->type, param));
      return new_tree;
    }

    PTR<Tree> visitMultiExprTree(const PTR<MultiExprTree>& tree, NullValue& param) override {
      auto new_tree = PTR<MultiExprTree>();
      new_tree->origin = tree->origin;
      new_tree->expressions = copyList<ExpressionTree>(tree->expressions, param);
      return new_tree;
    }

    PTR<Tree> visitAwaitTree(const PTR<AwaitTree>& tree, NullValue& param) override {
      auto new_tree = PTR<AwaitTree>();
      new_tree->origin = tree->origin;
      new_tree->expr = std::static_pointer_cast<ExpressionTree>(scan(tree->expr, param));
      return new_tree;
    }

    PTR<Tree> visitParensTree(const PTR<ParensTree>& tree, NullValue& param) override {
      auto new_tree = PTR<ParensTree>();
      new_tree->origin = tree->origin;
      new_tree->expr = std::static_pointer_cast<ExpressionTree>(scan(tree->expr, param));
      return new_tree;
    }

    PTR<Tree> visitUnaryTree(const PTR<UnaryTree>& tree, NullValue& param) override {
      auto new_tree = PTR<UnaryTree>();
      new_tree->origin = tree->origin;
      new_tree->expr = std::static_pointer_cast<ExpressionTree>(scan(tree->expr, param));
      new_tree->op_token = tree->op_token;
      return new_tree;
    }

    PTR<Tree> visitBinaryTree(const PTR<BinaryTree>& tree, NullValue& param) override {
      auto new_tree = PTR<BinaryTree>();
      new_tree->origin = tree->origin;
      new_tree->left = std::static_pointer_cast<ExpressionTree>(scan(tree->left, param));
      new_tree->right = std::static_pointer_cast<ExpressionTree>(scan(tree->right, param));
      new_tree->op_token = tree->op_token;
      return new_tree;
    }

    PTR<Tree> visitCodeTextTree(const PTR<CodeTextTree>& tree, NullValue& param) override {
      auto new_tree = PTR<CodeTextTree>();
      new_tree->origin = tree->origin;
      new_tree->content = tree->content;
      return new_tree;
    }

    PTR<Tree> visitCodeRefTree(const PTR<CodeRefTree>& tree, NullValue& param) override {
      auto new_tree = PTR<CodeRefTree>();
      new_tree->origin = tree->origin;
      new_tree->expr = std::static_pointer_cast<ExpressionTree>(scan(tree->expr, param));
      new_tree->ref_kind = tree->ref_kind;
      return new_tree;
    }

    PTR<Tree> visitErroneousTree(const PTR<ErroneousTree>& tree, NullValue& param) override {
      auto new_tree = PTR<ErroneousTree>();
      new_tree->origin = tree->origin;
      new_tree->tokens = tree->tokens;
      new_tree->msg = tree->msg;
      return new_tree;
    }

  private:
    template <typename T>
    List<PTR<T>> copyList(const List<PTR<T>>& list, NullValue& param) {
      List<PTR<T>> new_list;
      const size_t size = list.size();
      for (size_t i = 0; i < size; i++) {
        new_list.add(std::static_pointer_cast<T>(scan(list[i], param)));
      }
      return new_list;
    }
  };
} // namespace NS_TIEC

#endif // TIEC_TREE_COPIER_H
